package com.happy3w.bean.json;

import java.util.Objects;
import java.util.Stack;

public class JsonDecoder {
    private final JsonDecoderListener listener;
    private final StringDecoder stringDecoder = new StringDecoder();
    private final StringBuilder buf = new StringBuilder();

    private Stack<Character> bracketStack = new Stack<>();
    private boolean fieldNameExpected = false;
    private boolean inText = false;

    public JsonDecoder(JsonDecoderListener listener) {
        this.listener = listener;
    }

    public void accept(char in) {
        Character ch = stringDecoder.accept(in);
        if (stringDecoder.inString()) {
            if (ch == null) {
                return;
            }
            if (fieldNameExpected) {
                buf.append(ch);
            } else {
                if (!inText) {
                    listener.textStart();
                    inText = true;
                }
                listener.textAccept(ch);
            }
            return;
        }
        if (in == '\"') {
            if (!fieldNameExpected) {
                inText = false;
                listener.textEnd();
            }
            return;
        }

        if (in == '{') {
            bracketStack.push(in);
            listener.objectStart();
            fieldNameExpected = calculateFieldNameExpected();
        } else if (in == '}') {
            popBracket('{');
            tryAcceptValue();
            listener.objectEnd();
        } else if (in == '[') {
            bracketStack.push(in);
            listener.arrayStart();
        } else if (in == ']') {
            popBracket('[');
            tryAcceptValue();
            listener.arrayEnd();
        } else if (in == ':') {
            String currentFieldName = buf.toString();
            buf.setLength(0);
            listener.fieldStart(currentFieldName);
            fieldNameExpected = false;
        } else if (in == ',') {
            tryAcceptValue();
            fieldNameExpected = calculateFieldNameExpected();
        } else if (in == ' ' || in == '\t' || in == '\n' || in == '\r') {
            // ignore
        } else {
            buf.append(in);
        }
    }

    private boolean calculateFieldNameExpected() {
        if (bracketStack.isEmpty()) {
            return false;
        }
        if (bracketStack.peek() == '{') {
            return true;
        }
        return false;
    }

    private void popBracket(char expectedBracket) {
        if (bracketStack.isEmpty()) {
            throw new RuntimeException("Unexpected bracket:" + expectedBracket);
        }
        if (bracketStack.peek() != expectedBracket) {
            throw new RuntimeException("Unexpected bracket:" + expectedBracket);
        }
        bracketStack.pop();
    }

    private void tryAcceptValue() {
        String textValue = buf.toString().trim();
        buf.setLength(0);
        if (textValue.isEmpty()) {
            return;
        }

        if (Objects.equals("null", textValue)) {
            listener.valueAccept(null);
        } else if (Objects.equals("true", textValue)) {
            listener.valueAccept(true);
        } else if (Objects.equals("false", textValue)) {
            listener.valueAccept(false);
        } else {
            try {
                listener.valueAccept(Double.parseDouble(textValue));
            } catch (NumberFormatException e) {
                throw new RuntimeException("Failed parse value:" + textValue);
            }
        }
    }
}
